home *** CD-ROM | disk | FTP | other *** search
/ Night Owl 6 / Night Owl's Shareware - PDSI-006 - Night Owl Corp (1990).iso / 020a / dvpt20.zip / VPMOD.ASM < prev    next >
Assembly Source File  |  1991-12-12  |  16KB  |  763 lines

  1. page 58,132
  2. ;****************************************************************
  3. ;*                                                              *
  4. ;*             Digitized Voice Programmer's Toolkit             *
  5. ;*             ------------------------------------             *
  6. ;*                                                              *
  7. ;*                  Sound Playback Primitives                   *
  8. ;*                                                              *
  9. ;*            Copyright (c) 1991, Farpoint Software             *
  10. ;*                                                              *
  11. ;****************************************************************
  12.  
  13. .8086
  14. .MODEL LARGE
  15.  
  16. ;**********************************************************************
  17. ;*                                                                    *
  18. ;*                             Equates                                *
  19. ;*                                                                    *
  20. ;**********************************************************************
  21.  
  22. tccount        equ    72    ;reload count to produce 16572 Hz
  23. countmax_18hz    equ    910    ;ratio of new timer rate to standard rate
  24.  
  25. tcaddrc        equ    43h    ;timer/counter control register address
  26. tcaddrd0    equ    40h    ;timer/counter data register zero (sys clock)
  27. tcaddrd2    equ    42h    ;timer/counter data register two (spkr)
  28.  
  29. tcmode0a    equ    34h    ;two-byte mode for system clock timer
  30. tcmode0b    equ    24h    ;high-byte mode for system clock timer
  31. tcmode2a    equ    0B0h    ;mode for speaker timer (for initialization)
  32.  
  33. tclatchcmd0    equ    00h    ;latch command for reading timer 0
  34. tclatchcmd2    equ    80h    ;latch command for reading timer 2
  35.  
  36. ppiaddr        equ    61h    ;programmable peripheral interface address
  37.  
  38. kbdonly_mask    equ    0FDh    ;mask config to allow only keyboard interrupt
  39. intmaskaddr    equ    21h    ;address of the interrupt mask register
  40. kbdint_number    equ    9    ;vector number of the keyboard hardware int
  41.  
  42. ;CPU speed calibration initialization parameters
  43.  
  44. init_delayctr        equ    1024
  45. init_cal_increment    equ    512
  46. init_cal_passes        equ    10
  47.  
  48. ;**********************************************************************
  49. ;*                                                                    *
  50. ;*                         Global variables                           *
  51. ;*                                                                    *
  52. ;**********************************************************************
  53.  
  54. .DATA
  55.  
  56. ;CPU speed calibration data
  57.  
  58. delayctr    dw    init_delayctr-1
  59. cal_increment    dw    init_cal_increment
  60. cal_passes    dw    init_cal_passes
  61.  
  62. ;keyboard hardware interrupt occurrence flag
  63.  
  64. keyflag        db    0
  65.  
  66. ;interrupt controller mask save byte
  67.  
  68. original_mask    db    0
  69.  
  70. ;port 61h default configuration
  71.  
  72. ppibyte        db    0
  73.  
  74. ;**********************************************************************
  75. ;*                                                                    *
  76. ;*                               Code                                 *
  77. ;*                                                                    *
  78. ;**********************************************************************
  79.  
  80. .CODE
  81.  
  82.     assume    ds:DGROUP
  83.  
  84. ;code-segment storage for keyboard interrupt chain vector
  85.  
  86. original_kbdint_vec    dd    0
  87.  
  88. ;**********************************************************************
  89. ;*          Keyboard Hardware Interrupt Intercept Routine             *
  90. ;**********************************************************************
  91.  
  92. ;This routine sets the "keyflag" variable to 1 whenever a keyboard
  93. ; interrupt occurs.
  94.  
  95. kbdint    proc    far
  96.  
  97.     push    ds
  98.     push    ax
  99.     mov    ax,DGROUP
  100.     mov    ds,ax
  101.     mov    keyflag,1
  102.     pop    ax
  103.     pop    ds
  104.     jmp    dword ptr cs:original_kbdint_vec
  105.  
  106. kbdint    endp
  107.  
  108. ;**********************************************************************
  109. ;*              Calibrated software delay routine                     *
  110. ;**********************************************************************
  111.  
  112. ;lots of nops
  113.  
  114. delay    proc    near
  115.  
  116.     db    init_delayctr dup (90h)
  117.     ret                ;starting delay value
  118.     db    (init_delayctr-1) dup (90h)
  119.     ret                ;should never actually be executed
  120.  
  121. delay    endp
  122.  
  123. ;**********************************************************************
  124. ;*         Initialize timer for correct speaker operation.            *
  125. ;**********************************************************************
  126.  
  127. ;This procedure returns 0 if successful and 1 if unsuccessful.
  128.  
  129. set_ppibyte    proc    near
  130.  
  131. ;return value is kept in BX register
  132.  
  133.     mov    bx,0        ;set to 1 later if required
  134.  
  135. ;set gate 2 drive to one
  136.  
  137.     in    al,ppiaddr
  138.     and    al,0FDh
  139.     or    al,01h
  140.     mov    ppibyte,al
  141.     out    ppiaddr,al
  142.  
  143. ;set timer chip to mode zero (int on terminal count)
  144.  
  145.     mov    al,tcmode2a
  146.     cli
  147.     out    tcaddrc,al
  148.     jmp    short $+2
  149.     jmp    short $+2
  150.  
  151. ;set count to 0001h for quick termination
  152.  
  153.     mov    al,1
  154.     out    tcaddrd2,al
  155.     jmp    short $+2
  156.     jmp    short $+2
  157.     sub    al,al
  158.     out    tcaddrd2,al
  159.     sti
  160.     jmp    short $+2
  161.     jmp    short $+2
  162.  
  163. ;write the standard setup to timer zero
  164.  
  165.     mov    al,tcmode0a
  166.     cli
  167.     out    tcaddrc,al
  168.     jmp    short $+2
  169.     jmp    short $+2
  170.     mov    al,0
  171.     out    tcaddrd0,al
  172.     jmp    short $+2
  173.     jmp    short $+2
  174.     out    tcaddrd0,al
  175.     sti
  176.     jmp    short $+2
  177.     jmp    short $+2
  178.  
  179. ;set timer zero to high-byte mode
  180.  
  181.     mov    al,tcmode0b
  182.     cli
  183.     out    tcaddrc,al
  184.     jmp    short $+2
  185.     jmp    short $+2
  186.     mov    al,0
  187.     out    tcaddrd0,al
  188.     sti
  189.     jmp    short $+2
  190.     jmp    short $+2
  191.  
  192. ;wait for next timer tick
  193.  
  194.     mov    ah,0
  195.     mov    al,tclatchcmd0
  196.     cli
  197.     out    tcaddrc,al
  198.     jmp    short $+2
  199.     jmp    short $+2
  200.     in    al,tcaddrd0
  201.     sti
  202.     jmp    short $+2
  203.     jmp    short $+2
  204.     mov    dx,ax
  205. spi_sync:
  206.     mov    al,tclatchcmd0
  207.     cli
  208.     out    tcaddrc,al
  209.     jmp    short $+2
  210.     jmp    short $+2
  211.     in    al,tcaddrd0
  212.     sti
  213.     jmp    short $+2
  214.     cmp    ax,dx
  215.     mov    dx,ax
  216.     jbe    spi_sync
  217.  
  218. ;Wait for another timer tick (18Hz); this should be
  219. ; plenty of time for timer 2 to reach terminal count.
  220.  
  221. spi_wt1:
  222.     mov    al,tclatchcmd0
  223.     cli
  224.     out    tcaddrc,al
  225.     jmp    short $+2
  226.     jmp    short $+2
  227.     in    al,tcaddrd0
  228.     sti
  229.     sub    ah,ah
  230.     cmp    ax,dx
  231.     mov    dx,ax
  232.     jbe    spi_wt1
  233.  
  234. ;read the content of timer 2 and check for 0001h
  235.  
  236.     mov    al,tclatchcmd2
  237.     cli
  238.     out    tcaddrc,al
  239.     jmp    short $+2
  240.     jmp    short $+2
  241.     in    al,tcaddrd2
  242.     mov    ah,al
  243.     jmp    short $+2
  244.     jmp    short $+2
  245.     in    al,tcaddrd2
  246.     sti
  247.     or    al,al        ;test high byte for 00h
  248.     jnz    spi_countdone
  249.     cmp    ah,1        ;test low byte for 01h
  250.     jnz    spi_countdone
  251.  
  252. ;(arrive here if timer 2 is NOT counting)
  253. ;If timer 2 does not count,
  254. ; we flag an error and keep going.
  255.  
  256.     mov    bx,1
  257.  
  258. ;set gate 2 drive to zero (disable counting)
  259.  
  260. spi_countdone:
  261.     in    al,ppiaddr
  262.     and    al,0FCh
  263.     mov    ppibyte,al
  264.     out    ppiaddr,al
  265.  
  266. ;count is (supposedly) finished; test timer 2 output readback bit
  267.  
  268.     in    al,ppiaddr
  269.     test    al,20h        ;bit 5 should be high
  270.     jnz    spi_done
  271.  
  272. ;wrong polarity, set error flag and continue
  273.  
  274.     mov    bx,1
  275.  
  276. spi_done:
  277.     mov    ax,bx
  278.     ret
  279.  
  280. set_ppibyte    endp
  281.  
  282. ;**********************************************************************
  283. ;*                    Set Calibration Constant                        *
  284. ;*                                                                    *
  285. ;*       This call simply sets the calibration constant to the        *
  286. ;*       value specified in the parameter.                            *
  287. ;**********************************************************************
  288.  
  289. ;Accepts the following parameters with PASCAL parameter-passing convention:
  290.  
  291. ;Position   Size   Description
  292. ;--------   ----   -----------
  293. ;   1         2    The calibration constant to be set.
  294.  
  295. ;There is no return value.
  296.  
  297. psc_parm1    equ    [bp+6]    ;length = 2
  298.  
  299. psc_parmlength    equ    2
  300.  
  301.     public    PSETCAL
  302.  
  303. PSETCAL        proc    far
  304.  
  305.     push    bp
  306.     mov    bp,sp
  307.  
  308. ;in case this routine has been called before, erase the "ret" instruction
  309. ; that was placed in the delay routine
  310.  
  311.     inc    delayctr        ;since we decremented it before
  312.     lea    bx,delay
  313.     add    bx,delayctr
  314.     mov    byte ptr cs:[bx],90h    ;this is a "nop" instruction
  315.  
  316. ;get the new delay parameter
  317.  
  318.     mov    ax,psc_parm1
  319.     mov    delayctr,ax
  320.  
  321. ;insert a "ret" instruction at the new location in the delay routine
  322.  
  323.     lea    bx,delay
  324.     add    bx,delayctr
  325.     mov    byte ptr cs:[bx],0C3h    ;this is a "ret" instruction
  326.  
  327.     dec    delayctr        ;for benefit of playback routine
  328.  
  329.     pop    bp
  330.     ret    psc_parmlength
  331.  
  332. PSETCAL        endp
  333.  
  334. ;**********************************************************************
  335. ;*                      Calibration procedure                         *
  336. ;*                                                                    *
  337. ;*      This routine must be called once before any calls are made    *
  338. ;*      to the playback routine. It measures the CPU execution        *
  339. ;*      speed and saves compensation values.                          *
  340. ;**********************************************************************
  341.  
  342. ;There are no entry parameters.
  343.  
  344. ;The return value is a double word, defined as follows:
  345.  
  346. ;Return value low word        Meaning
  347. ;---------------------        -------
  348. ;     0                       success
  349. ;     1                       this CPU is too slow to accomplish
  350. ;                              normal-speed playback
  351. ;     2                       operation not possible under Windows
  352. ;                              in "Enhanced" mode
  353. ;     3                       unusual speaker drive circuit encountered,
  354. ;                              might not output sound
  355.  
  356. ;The return value high word is the actual speed calibration constant
  357. ; for this computer
  358.  
  359. cal_temp1    equ    [bp-2]    ;length = 2
  360.  
  361. cal_locallength    equ    2
  362.  
  363.     public    PCALIBRATE
  364.  
  365. PCALIBRATE    proc    far
  366.  
  367.     push    bp
  368.     mov    bp,sp
  369.     sub    sp,cal_locallength
  370.     push    si
  371.     push    di
  372.  
  373. ;test for the "Enhanced Mode Windows" environment
  374.  
  375.     mov    ax,1600h
  376.     int    2Fh
  377.     cmp    al,00h
  378.     je    cal_no_enh_win
  379.     cmp    al,80h
  380.     je    cal_no_enh_win
  381.     mov    delayctr,1
  382.     mov    ax,2
  383.     jmp    cal_exit
  384. cal_no_enh_win:
  385.  
  386. ;test speaker drive circuitry and set configuration
  387.  
  388.     call    set_ppibyte
  389.     mov    cal_temp1,ax
  390.  
  391. ;in case this routine has been called before, erase the "ret" instruction
  392. ; that was placed in the delay routine
  393.  
  394.     inc    delayctr        ;since we decremented it before
  395.     lea    bx,delay
  396.     add    bx,delayctr
  397.     mov    byte ptr cs:[bx],90h    ;this is a "nop" instruction
  398.  
  399. ;initialize parameters
  400.  
  401.     mov    delayctr,init_delayctr
  402.     mov    cal_increment,init_cal_increment
  403.     mov    cal_passes,init_cal_passes
  404.  
  405. ;insert a "ret" instruction into the starting location in the delay routine
  406.  
  407.     lea    bx,delay
  408.     add    bx,delayctr
  409.     mov    byte ptr cs:[bx],0C3h    ;this is a "ret" instruction
  410.  
  411. ;no interrupts until we are through calibrating
  412.  
  413.     cli
  414.  
  415. ;write the standard setup to timer zero
  416.  
  417.     mov    al,tcmode0a
  418.     out    tcaddrc,al
  419.     jmp    short $+2
  420.     jmp    short $+2
  421.     mov    al,0
  422.     out    tcaddrd0,al
  423.     jmp    short $+2
  424.     jmp    short $+2
  425.     out    tcaddrd0,al
  426.     jmp    short $+2
  427.     jmp    short $+2
  428.  
  429. ;set timer zero to high-byte mode
  430.  
  431.     mov    al,tcmode0b
  432.     out    tcaddrc,al
  433.     jmp    short $+2
  434.     jmp    short $+2
  435.     mov    al,0
  436.     out    tcaddrd0,al
  437.     jmp    short $+2
  438.     jmp    short $+2
  439.  
  440. ;initialize loop count
  441.  
  442. cal_zerocnt:
  443.     sub    cx,cx
  444.  
  445. ;synchronize to timer tick
  446.  
  447.     mov    ah,0
  448.     mov    al,tclatchcmd0
  449.     out    tcaddrc,al
  450.     jmp    short $+2
  451.     jmp    short $+2
  452.     in    al,tcaddrd0
  453.     jmp    short $+2
  454.     jmp    short $+2
  455.     mov    si,ax
  456. cal_sync:
  457.     mov    al,tclatchcmd0
  458.     out    tcaddrc,al
  459.     jmp    short $+2
  460.     jmp    short $+2
  461.     in    al,tcaddrd0
  462.     jmp    short $+2
  463.     cmp    ax,si
  464.     mov    si,ax
  465.     jbe    cal_sync
  466.  
  467. ;calibration loop
  468.  
  469. cal_dummyloop:
  470.     mov    al,80h
  471.     nop
  472.     nop
  473.     or    ax,ax
  474.     jz    cal_scale
  475. cal_scale:
  476.     sub    ah,ah
  477.     mov    bx,1        ;dummy value
  478.     mul    bx
  479.     mov    al,ah
  480.     mov    ah,dl
  481.     lea    bx,delay
  482.     add    bx,ax
  483.     mov    word ptr cs:[bx],9090h    ;these are "nop" instructions
  484.     mov    al,ppibyte
  485.     or    al,00h
  486.     out    ppiaddr,al
  487.     and    al,0FFh
  488.     call    delay
  489.     mov    word ptr cs:[bx],9090h    ;these are "nop" instructions
  490.     inc    cx
  491.     mov    al,tclatchcmd0
  492.     out    tcaddrc,al
  493.     jmp    short $+2
  494.     jmp    short $+2
  495.     in    al,tcaddrd0
  496.     sub    ah,ah
  497.     nop
  498.     nop
  499.     cmp    ax,si
  500.     mov    si,ax
  501.     nop
  502.     nop
  503.     jbe    cal_dummyloop
  504.  
  505. ;kill the old "ret" instruction in the delay routine
  506.  
  507.     lea    bx,delay
  508.     add    bx,delayctr
  509.     mov    byte ptr cs:[bx],90h    ;this is a "nop" instruction
  510.  
  511. ;adjust delay counter
  512.  
  513.     mov    ax,cal_increment
  514.     shr    cal_increment,1
  515.     cmp    cx,countmax_18hz
  516.     je    cal_setret
  517.     jb    cal_makefaster
  518.     add    delayctr,ax
  519.     jmp    cal_setret
  520. cal_makefaster:
  521.     sub    delayctr,ax
  522. cal_setret:                ;here we insert a new return
  523.     lea    bx,delay
  524.     add    bx,delayctr
  525.     mov    byte ptr cs:[bx],0C3h    ;this is a "ret" instruction
  526.     dec    cal_passes
  527.     cmp    cal_passes,0
  528.     je    cal_restore
  529.     jmp    cal_zerocnt
  530.  
  531. ;repair loss of system timer ticks and restore interrupts
  532.  
  533. cal_restore:
  534.     mov    bx,40h
  535.     mov    es,bx
  536.     mov    bx,6Ch
  537.     add    word ptr es:[bx],init_cal_passes+1
  538.     adc    word ptr es:[bx+2],0
  539.     cmp    word ptr es:[bx+2],18h
  540.     jb    cal_tc_end
  541.     ja    cal_tc_midnite
  542.     cmp    word ptr es:[bx],0B0h
  543.     jb    cal_tc_end
  544. cal_tc_midnite:
  545.     mov    byte ptr es:[bx+4],1
  546.     sub    word ptr es:[bx],0B0h
  547.     sbb    word ptr es:[bx+2],18h
  548. cal_tc_end:
  549.     sti
  550.  
  551. ;determine if CPU is too slow
  552.  
  553.     dec    delayctr        ;for benefit of playback routine
  554.     cmp    delayctr,64
  555.     jb    cal_cpuslow
  556.     sub    ax,ax
  557.     jmp    cal_exit
  558. cal_cpuslow:
  559.     mov    ax,1
  560. cal_exit:
  561.     or    ax,ax
  562.     jnz    cal_rdc
  563.     cmp    word ptr cal_temp1,0
  564.     jz    cal_rdc
  565.     mov    ax,3
  566. cal_rdc:
  567.     mov    dx,delayctr
  568.     pop    di
  569.     pop    si
  570.     add    sp,cal_locallength
  571.     pop    bp
  572.     ret
  573.  
  574. PCALIBRATE    endp
  575.  
  576. ;**********************************************************************
  577. ;*                        Playback procedure                          *
  578. ;**********************************************************************
  579.  
  580. ;Accepts the following parameters with PASCAL parameter-passing convention:
  581.  
  582. ;Position   Size   Description
  583. ;--------   ----   -----------
  584. ;   1         4    A far pointer to the memory block used for voice data.
  585. ;   2         4    A dword indicating the length of the memory block.
  586.  
  587. ;The return value is a dword indicating the number of bytes actually played.
  588.  
  589. pv_parm1    equ    [bp+10]    ;length = 4
  590. pv_parm2_hi    equ    [bp+8]    ;length = 2
  591. pv_parm2_lo    equ    [bp+6]    ;length = 2
  592.  
  593. pv_parmlength    equ    8
  594.  
  595. pv_temp1    equ    [bp-2]    ;length = 2
  596. pv_temp2    equ    [bp-4]    ;length = 2
  597.  
  598. pv_locallength    equ    4
  599.  
  600.     public    PLAYVOICE
  601.  
  602. PLAYVOICE    proc    far
  603.  
  604.     push    bp
  605.     mov    bp,sp
  606.     sub    sp,pv_locallength
  607.     push    si
  608.     push    di
  609.  
  610. ;setup speaker and timer 2
  611.  
  612.     call    set_ppibyte
  613.  
  614. ;mask all interrupts except the keyboard
  615.  
  616.     cli
  617.  
  618.     in    al,intmaskaddr
  619.     mov    original_mask,al
  620.     mov    al,kbdonly_mask
  621.     out    intmaskaddr,al
  622.  
  623. ;install the keyboard interrupt intercept routine
  624.  
  625.     sub    ax,ax
  626.     mov    es,ax
  627.     mov    bx,kbdint_number*4
  628.     mov    ax,es:[bx]
  629.     mov    word ptr cs:original_kbdint_vec,ax
  630.     mov    ax,es:[bx+2]
  631.     mov    word ptr cs:original_kbdint_vec+2,ax
  632.     lea    ax,kbdint
  633.     mov    es:[bx],ax
  634.     mov    ax,cs
  635.     mov    es:[bx+2],ax
  636.  
  637.     sti
  638.  
  639. ;set up initial conditions
  640.  
  641.     mov    keyflag,0
  642.     les    si,dword ptr pv_parm1
  643.     mov    cx,pv_parm2_lo
  644.     mov    di,pv_parm2_hi
  645.     jcxz    pv_playloop
  646.     inc    di
  647.  
  648. ;playback loop
  649.  
  650. pv_playloop:
  651.     mov    al,es:[si]
  652.     inc    si
  653.     jz    pv_incrseg
  654. pv_scale:
  655.     sub    ah,ah
  656.     mov    bx,delayctr
  657.     mul    bx
  658.     mov    al,ah
  659.     mov    ah,dl
  660.     lea    bx,delay
  661.     add    bx,ax
  662.     mov    word ptr cs:[bx],61E6h    ;this is an "out 61h,al" instruction
  663.     mov    al,ppibyte
  664.     or    al,02h
  665.     out    ppiaddr,al
  666.     and    al,0FDh
  667.     call    delay
  668.     mov    word ptr cs:[bx],9090h    ;these are "nop" instructions
  669.     nop
  670.     mov    al,tclatchcmd0
  671.     out    tcaddrc,al
  672.     jmp    short $+2
  673.     jmp    short $+2
  674.     in    al,tcaddrd0
  675.     sub    ah,ah
  676.     cmp    keyflag,0
  677.     jne    pv_stop
  678.     loop    pv_playloop
  679.     dec    di
  680.     jnz    pv_playloop
  681. pv_stop:
  682.     jmp    short pv_fixkbi
  683.  
  684. pv_incrseg:
  685.     mov    bx,es
  686.     add    bx,1000h
  687.     mov    es,bx
  688.     jmp    short pv_scale
  689.  
  690. ;remove the keyboard interrupt intercept routine
  691.  
  692. pv_fixkbi:
  693.     cli
  694.  
  695.     sub    ax,ax
  696.     mov    es,ax
  697.     mov    bx,kbdint_number*4
  698.     mov    ax,word ptr cs:original_kbdint_vec
  699.     mov    es:[bx],ax
  700.     mov    ax,word ptr cs:original_kbdint_vec+2
  701.     mov    es:[bx+2],ax
  702.  
  703. ;restore the original interrupt mask
  704.  
  705.     mov    al,original_mask
  706.     out    intmaskaddr,al
  707.  
  708.     sti
  709.  
  710. ;leave speaker in a known state
  711.  
  712.     mov    al,ppibyte
  713.     out    ppiaddr,al
  714.  
  715. ;calculate the number of bytes played
  716.  
  717.     jcxz    pv_calcbytes
  718.     dec    di
  719. pv_calcbytes:
  720.     mov    ax,pv_parm2_lo
  721.     mov    dx,pv_parm2_hi
  722.     sub    ax,cx
  723.     sbb    dx,di
  724.     mov    pv_temp2,ax
  725.     mov    pv_temp1,dx
  726.  
  727. ;compensate for lost timer ticks
  728.  
  729.     mov    cx,countmax_18hz
  730.     div    cx
  731.     mov    bx,40h
  732.     mov    es,bx
  733.     mov    bx,6Ch
  734.     cli
  735.     add    es:[bx],ax
  736.     adc    word ptr es:[bx+2],0
  737.     cmp    word ptr es:[bx+2],18h
  738.     jb    pv_tc_end
  739.     ja    pv_tc_midnite
  740.     cmp    word ptr es:[bx],0B0h
  741.     jb    pv_tc_end
  742. pv_tc_midnite:
  743.     mov    byte ptr es:[bx+4],1
  744.     sub    word ptr es:[bx],0B0h
  745.     sbb    word ptr es:[bx+2],18h
  746. pv_tc_end:
  747.     sti
  748.  
  749. ;set the return value
  750.  
  751.     mov    ax,pv_temp2
  752.     mov    dx,pv_temp1
  753.  
  754.     pop    di
  755.     pop    si
  756.     add    sp,pv_locallength
  757.     pop    bp
  758.     ret    pv_parmlength
  759.  
  760. PLAYVOICE    endp
  761.  
  762.     end
  763.